﻿//---------------------------------------------------------------------------
// <copyright file="RibbonQuickAccessToolBar.cs" company="Microsoft Corporation">
//     Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//---------------------------------------------------------------------------

namespace Microsoft.Windows.Controls.Ribbon
{
    #region Using declarations

    using System;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Data;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Threading;

    #endregion

    #region RibbonQATPlacement enum

    /// <summary>
    ///   An enumeration of possible locations for the QuickAccessToolbar.
    /// </summary>
    public enum RibbonQATPlacement
    {
        /// <summary>
        ///   Indicates that the QuickAccessToolbar should be displayed in the toolbar.
        /// </summary>
        InToolBar,

        /// <summary>
        ///   Inidcates that the QuickAccessToolbar should be displayed in menu.
        /// </summary>
        InCustomizeMenu,

        /// <summary>
        ///   Indicates that the QuickAccessToolbar should be displayed in the toolbar and in menu.
        /// </summary>
        InCustomizeMenuAndToolBar,
    }

    #endregion

    /// <summary>
    ///   Implements the Ribbon's QuickAccessToolbar.
    /// </summary>
    public class RibbonQuickAccessToolBar : ItemsControl
    {
        #region Fields

        /// <summary>
        ///   The main layout panel for the QuickAccessToolbar.
        /// </summary>
        private Panel _mainPanel;

        /// <summary>
        ///   The layout panel for the overflowed items of the QuickAccessToolbar.
        /// </summary>
        private Panel _overflowPanel;

        /// <summary>
        ///   The Customize menu for the QuickAccessToolbar.
        /// </summary>
        private MenuBase _customizeMenu;

        /// <summary>
        ///   A special menu item which toggles placement of the QuickAccessToolbar.
        /// </summary>
        private RibbonMenuItem _menuItemShowQAT;

        #endregion

        #region Constructors

        /// <summary>
        ///   Initializes static members of the RibbonQuickAccessToolBar class.
        /// </summary>
        static RibbonQuickAccessToolBar()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonQuickAccessToolBar), new FrameworkPropertyMetadata(typeof(RibbonQuickAccessToolBar)));
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets or sets a command that is executed when the "More Commands" option is chosen
        ///   from the QuickAccessToolbar customization menu.  If this command is not set, the
        ///   "More Commands" option will not be displayed in the Customize menu.
        /// </summary>
        public ICommand AdvancedCustomizationCommand
        {
            get { return (ICommand)GetValue(AdvancedCustomizationCommandProperty); }
            set { SetValue(AdvancedCustomizationCommandProperty, value); }
        }

        /// <summary>
        ///   Using a DependencyProperty as the backing store for AdvancedCustomizationCommand.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty AdvancedCustomizationCommandProperty =
                    DependencyProperty.Register(
                            "AdvancedCustomizationCommand",
                            typeof(ICommand),
                            typeof(RibbonQuickAccessToolBar),
                            new FrameworkPropertyMetadata(null));

        /// <summary>
        ///   Gets an enum indicating the placement of the QuickAccessToolbar.
        /// </summary>
        /// <param name="obj">The object to get the Placement property from.</param>
        /// <returns>An enum indicating the placement of the QuickAccessToolbar.</returns>
        [AttachedPropertyBrowsableForChildren()]
        public static RibbonQATPlacement GetPlacement(DependencyObject obj)
        {
            return (RibbonQATPlacement)obj.GetValue(PlacementProperty);
        }

        /// <summary>
        ///   Sets an enum indicating the placement of the QuickAccessToolbar.
        /// </summary>
        /// <param name="obj">The QuickAccessToolbar whose placemnt to set.</param>
        /// <param name="value">The new placement.</param>
        public static void SetPlacement(DependencyObject obj, RibbonQATPlacement value)
        {
            obj.SetValue(PlacementProperty, value);
        }

        /// <summary>
        ///   Using an attached DependencyProperty as the backing store for Placement.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty PlacementProperty =
                    DependencyProperty.RegisterAttached(
                            "Placement",
                            typeof(RibbonQATPlacement),
                            typeof(RibbonQuickAccessToolBar),
                            new FrameworkPropertyMetadata(RibbonQATPlacement.InToolBar, new PropertyChangedCallback(OnPlacementChanged)));

        /// <summary>
        ///   Gets or sets a value indicating whether 
        /// </summary>
        [Bindable(true), Category("Appearance")]
        public bool CanUserCustomize
        {
            get { return (bool)GetValue(CanUserCustomizeProperty); }
            set { SetValue(CanUserCustomizeProperty, value); }
        }

        /// <summary>
        ///   Using a DependencyProperty as the backing store for CanUserCustomize.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty CanUserCustomizeProperty =
                    DependencyProperty.Register(
                            "CanUserCustomize",
                            typeof(bool),
                            typeof(RibbonQuickAccessToolBar),
                            new UIPropertyMetadata(true));

        /// <summary>
        ///   Gets or sets a value indicating whether or not the overflow popup is currently open.
        /// </summary>
        [Bindable(true), Browsable(false), Category("Appearance")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool IsOverflowOpen
        {
            get { return (bool)GetValue(IsOverflowOpenProperty); }
            set { SetValue(IsOverflowOpenProperty, value); }
        }

        /// <summary>
        ///   Using a DependencyProperty as the backing store for IsOverflowOpen.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty IsOverflowOpenProperty = DependencyProperty.Register(
            "IsOverflowOpen",
            typeof(bool),
            typeof(RibbonQuickAccessToolBar),
            new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnOverflowOpenChanged)));

        /// <summary>
        ///   Gets a value indicating whether we have overflow items.
        /// </summary>
        public bool HasOverflowItems
        {
            get { return (bool)GetValue(HasOverflowItemsProperty); }
            private set { SetValue(HasOverflowItemsPropertyKey, value); }
        }
        
        /// <summary>
        ///   The DependencyPropertyKey needed to set read-only HasOverflowItems property.
        /// </summary>
        private static readonly DependencyPropertyKey HasOverflowItemsPropertyKey =
                DependencyProperty.RegisterReadOnly(
                        "HasOverflowItems",
                        typeof(bool),
                        typeof(RibbonQuickAccessToolBar),
                        new FrameworkPropertyMetadata(false));

        /// <summary>
        ///   Using a DependencyProperty as the backing store for HasOverflowItems.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty HasOverflowItemsProperty =
                HasOverflowItemsPropertyKey.DependencyProperty;

        #endregion

        #region Private Properties

        /// <summary>
        ///   Gets or sets a value indicating whether the QuickAccessToolbar is shown on
        ///   bottom and top.
        /// </summary>
        private bool ShowQuickAccessToolbarOnTop
        {
            get { return (bool)GetValue(ShowQuickAccessToolbarOnTopProperty); }
            set { SetValue(ShowQuickAccessToolbarOnTopProperty, value); }
        }

        /// <summary>
        ///   Using a DependencyProperty as the backing store for ShowQuickAccessToolbarOnTop.  This enables animation, styling, binding, etc...
        /// </summary>
        private static readonly DependencyProperty ShowQuickAccessToolbarOnTopProperty = DependencyProperty.Register(
            "ShowQuickAccessToolbarOnTop",
            typeof(bool),
            typeof(RibbonQuickAccessToolBar),
            new FrameworkPropertyMetadata(true, new PropertyChangedCallback(OnShowQuickAccessToolbarOnTopChanged)));

        /// <summary>
        ///   Gets the parent Ribbon of the QuickAccessToolbar.
        /// </summary>
        private Ribbon Ribbon
        {
            get { return this.Parent as Ribbon; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        ///   Invoked when the QuickAccessToolbar's template is applied.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (_mainPanel != null)
            {
                _mainPanel.Children.Clear();
            }

            _mainPanel = GetTemplateChild("PART_MainPanel") as Panel;

            if (_overflowPanel != null)
            {
                _overflowPanel.Children.Clear();
            }

            _overflowPanel = GetTemplateChild("PART_OverflowPanel") as Panel;
            _customizeMenu = GetTemplateChild("PART_CustomizeMenu") as MenuBase;

            this.InitializeCustomizeMenu();
        }

        #endregion

        #region Protected Methods

        /// <summary>
        ///   Called when the QuickAccessToolbar is clicked, in this case we close the overflow.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            base.OnMouseDown(e);
            this.CloseOverflow();
        }

        /// <summary>
        ///   Called when the QuickAccessTooblar loses mouse capture, in this case we 
        ///   close the overflow.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected override void OnLostMouseCapture(MouseEventArgs e)
        {
            base.OnLostMouseCapture(e);

            // ToolBar has a capture when its overflow panel is open
            // close the overflow panel is case capture is set to null
            if (Mouse.Captured == null)
            {
                this.CloseOverflow();
            }
        }

        /// <summary>
        ///   Measures the children of the QAT to determine its desired size.
        /// </summary>
        /// <param name="constraint">The available size for layout.</param>
        /// <returns>The desired size of the QuickAccessToolbar.</returns>
        protected override Size MeasureOverride(Size constraint)
        {
            this.CloseOverflow();
            Size currentSize = new Size();

            // Perform 1 or 2 measure passes depend on HasOverflowItems change
            for (int i = 1; i <= 2; i++) 
            {
                // We should measure how may items we can fit into the constraint
                // Items that does not fit should go to the overflow collection

                // Step 1: Clear both panels
                _mainPanel.Children.Clear();
                _overflowPanel.Children.Clear();

                // Step 2: Add all items to the main panel
                foreach (UIElement item in Items)
                {
                    if (item != null)
                    {
                        RibbonGroup.SetControlSizeDefinition(item, new RibbonControlSizeDefinition() { ImageSize = RibbonImageSize.Small, IsLabelVisible = false });
                        _mainPanel.Children.Add(item);
                        this.InvalidateAncestorMeasure(item);
                        Size size = base.MeasureOverride(constraint);
                    }
                }

                // Step 3: Move all items that do not fit to overflow panel
                currentSize = base.MeasureOverride(new Size(double.PositiveInfinity, constraint.Height)); // chrome size without elements
                while (currentSize.Width > constraint.Width)
                {
                    // No more items to remove
                    if (_mainPanel.Children.Count == 0)
                    {
                        break;
                    }

                    UIElement item = _mainPanel.Children[_mainPanel.Children.Count - 1];
                    this.InvalidateAncestorMeasure(item);
                    _mainPanel.Children.Remove(item);
                    _overflowPanel.Children.Insert(0, item);
                    currentSize = base.MeasureOverride(new Size(double.PositiveInfinity, constraint.Height));
                }

                // When HasOverflowItems changes we need to run another measure pass
                if (this.HasOverflowItems == _overflowPanel.Children.Count > 0)
                {
                    break;
                }

                this.HasOverflowItems = !this.HasOverflowItems;
            }
            
            return currentSize;
        }

        /// <summary>
        ///   Invoked when the QuickAccessToolbar's items collection changes.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnItemsChanged(e);
            this.InitializeCustomizeMenu();
            InvalidateMeasure();
        }

        #endregion

        #region Private Methods

        /// <summary>
        ///   Updates whether the QuickAccessToolbar is shown on bottom or top.   
        /// </summary>
        /// <param name="d">The QuickAccessToolbar.</param>
        /// <param name="e">The event data.</param>
        private static void OnShowQuickAccessToolbarOnTopChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RibbonQuickAccessToolBar qat = d as RibbonQuickAccessToolBar;
            qat.UpdateShowQATMenuItem();
        }

        /// <summary>
        ///   Invoked when the Placement property changes.
        /// </summary>
        /// <param name="d">The RibbonQuickAccessToolbar whose Placement property changed.</param>
        /// <param name="e">The event data.</param>
        private static void OnPlacementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement fe = d as FrameworkElement;
            if (fe != null)
            {
                fe.Visibility = ((RibbonQATPlacement)e.NewValue == RibbonQATPlacement.InCustomizeMenu) ? Visibility.Collapsed : Visibility.Visible;
                RibbonQuickAccessToolBar qat = fe.Parent as RibbonQuickAccessToolBar;
                if (qat != null)
                {
                    qat.CloseOverflow();
                }
            }
        }

        /// <summary>
        ///   Invoked when the overflow section of the QuickAccessToolbar is shown
        ///   or hidden.
        /// </summary>
        /// <param name="element">The QuickAccessToolbar.</param>
        /// <param name="e">The event data.</param>
        private static void OnOverflowOpenChanged(DependencyObject element, DependencyPropertyChangedEventArgs e)
        {
            RibbonQuickAccessToolBar toolBar = (RibbonQuickAccessToolBar)element;

            if ((bool)e.NewValue)
            {
                // When the drop down opens, take capture
                Mouse.Capture(toolBar, CaptureMode.SubTree);
                toolBar.SetFocusOnToolBarOverflowPanel();
            }
            else
            {
                // If focus is still within the ToolBarOverflowPanel, make sure we the focus is restored to the main focus scope
                if (toolBar._overflowPanel != null && toolBar._overflowPanel.IsKeyboardFocusWithin)
                {
                    Keyboard.Focus(null);
                }

                if (Mouse.Captured == toolBar)
                {
                    Mouse.Capture(null);
                }
            }
        }

        /// <summary>
        ///   Closes the overflow section of the QuickAccessToolbar.
        /// </summary>
        private void CloseOverflow()
        {
            if (this.IsOverflowOpen)
            {
                ClearValue(IsOverflowOpenProperty);

                // Force Close in case of triggers
                if (this.IsOverflowOpen)
                {
                    this.IsOverflowOpen = false;
                }
            }
        }

        /// <summary>
        ///   Sets focus on the QuickAccessToolbar's overflow panel.
        /// </summary>
        private void SetFocusOnToolBarOverflowPanel()
        {
            Dispatcher.BeginInvoke(
                DispatcherPriority.Input, 
                new DispatcherOperationCallback(delegate(object param)
                {
                    if (_overflowPanel != null)
                    {
                        _overflowPanel.Focus();
                    }

                    return null;
                }),
                null);
        }

        /// <summary>
        ///   Walks up the Visual tree to invlaidate parents of the given Visual.
        /// </summary>
        /// <param name="obj">The object whose visual parents to invlidate.</param>
        private void InvalidateAncestorMeasure(DependencyObject obj)
        {
            UIElement uiObj = obj as UIElement;
            if (uiObj != null)
            {
                uiObj.InvalidateMeasure();
            }

            if (obj == this || obj == null)
            {
                return;
            }

            this.InvalidateAncestorMeasure(VisualTreeHelper.GetParent(obj));
        }

        /// <summary>
        ///   Initializes items of the Customize menu.
        /// </summary>
        private void InitializeCustomizeMenu()
        {
            if (_customizeMenu == null)
            {
                return;
            }

            _customizeMenu.Items.Clear();

            // Loop thru all Items and create a MenuItem if CustomizeMenuState is set
            foreach (DependencyObject item in Items)
            {
                if (item != null)
                {
                    RibbonQATPlacement customizeMenuState = GetPlacement(item);
                    if (customizeMenuState != RibbonQATPlacement.InToolBar)
                    {
                        RibbonMenuItem menuItem = new RibbonMenuItem();
                        menuItem.IsCheckable = true;
                        Binding binding = new Binding();
                        binding.Path = new PropertyPath("(0)", RibbonQuickAccessToolBar.PlacementProperty);
                        binding.Source = item;
                        binding.Mode = BindingMode.TwoWay;
                        binding.Converter = new CustomizeMenuStateToBooleanConverter();
                        menuItem.SetBinding(RibbonMenuItem.IsCheckedProperty, binding);

                        binding = new Binding("Command.LabelTitle");
                        binding.Source = item;
                        menuItem.SetBinding(RibbonMenuItem.HeaderProperty, binding);

                        _customizeMenu.Items.Add(menuItem);
                    }
                }
            }

            // Add separator after the items
            if (_customizeMenu.Items.Count > 0)
            {
                _customizeMenu.Items.Add(new Separator());
            }

            RibbonMenuItem mi;
            if (this.AdvancedCustomizationCommand != null)
            {
                mi = new RibbonMenuItem();
                mi.Header = SR.Get(SRID.Ribbon_CustomizeCommands);
                mi.Command = this.AdvancedCustomizationCommand; // TODO: Binding
                _customizeMenu.Items.Add(mi);
            }

            _menuItemShowQAT = new RibbonMenuItem();
            if (Ribbon != null)
            {
                this.UpdateShowQATMenuItem();
                Binding binding = new Binding("ShowQuickAccessToolbarOnTop");
                binding.Source = Ribbon;
                binding.Mode = BindingMode.TwoWay;
                _menuItemShowQAT.Click += new RoutedEventHandler(ShowQuickAccessToolbarClick);
                this.SetBinding(ShowQuickAccessToolbarOnTopProperty, binding);
            }

            _customizeMenu.Items.Add(_menuItemShowQAT);

            if (this.AdvancedCustomizationCommand != null)
            {
                _customizeMenu.Items.Add(new Separator());
            }

            mi = new RibbonMenuItem();
            mi.IsCheckable = true;
            mi.Header = SR.Get(SRID.Ribbon_Minimize);
            if (Ribbon != null)
            {
                Binding binding = new Binding("IsMinimized");
                binding.Source = Ribbon;
                binding.Mode = BindingMode.TwoWay;
                mi.SetBinding(RibbonMenuItem.IsCheckedProperty, binding);
            }

            _customizeMenu.Items.Add(mi);

            _customizeMenu.ApplyTemplate();
            TextBlock tb = _customizeMenu.Template.FindName("PART_CustomizeTitle", _customizeMenu) as TextBlock;
            if (tb != null)
            {
                tb.Text = SR.Get(SRID.Ribbon_CustomizeQATTitle);
            }
        }

        /// <summary>
        ///   Updates a message indicating whether show QAT above/below the Ribbon.
        /// </summary>
        private void UpdateShowQATMenuItem()
        {
            if (_menuItemShowQAT != null)
            {
                _menuItemShowQAT.Header = SR.Get(this.ShowQuickAccessToolbarOnTop ? SRID.Ribbon_ShowQATBelow : SRID.Ribbon_ShowQATAbove);
            }
        }

        /// <summary>
        ///   Toggles whether the QuickAccessToolbar should be displayed on bottom or on top.
        /// </summary>
        /// <param name="sender">The sender of the click event.</param>
        /// <param name="e">The event data.</param>
        private void ShowQuickAccessToolbarClick(object sender, RoutedEventArgs e)
        {
            this.ShowQuickAccessToolbarOnTop = !this.ShowQuickAccessToolbarOnTop;
        }

        #endregion

        #region CustomizeMenuStateToBooleanConverter

        /// <summary>
        ///   A class which converts between RibbonQATPlacement enum values and booleans.
        /// </summary>
        private sealed class CustomizeMenuStateToBooleanConverter : IValueConverter
        {
            /// <summary>
            ///   Convert from a RibbonQATPlacement enum to a boolean.
            /// </summary>
            /// <param name="value">The value to convert.</param>
            /// <param name="targetType">The target type to convert to.</param>
            /// <param name="parameter">A parameter to the converter.</param>
            /// <param name="culture">The culture to use for conversion.</param>
            /// <returns>The converted boolean value.</returns>
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                return ((RibbonQATPlacement)value) == RibbonQATPlacement.InCustomizeMenuAndToolBar;
            }

            /// <summary>
            ///   Convert from a boolean to a RibbonQATPlacement enum.
            /// </summary>
            /// <param name="value">The value to convert.</param>
            /// <param name="targetType">The target type to convert to.</param>
            /// <param name="parameter">A parameter to the converter.</param>
            /// <param name="culture">The culture to use for conversion.</param>
            /// <returns>The converted RibbonQATPlacement.</returns>
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                return (bool)value ? RibbonQATPlacement.InCustomizeMenuAndToolBar : RibbonQATPlacement.InCustomizeMenu;
            }
        }

        #endregion
    }

    #region RibbonToolBarStackPanel class

    /// <summary>
    ///   The QuickAccessToolbar's internal stack panel.
    /// </summary>
    public class RibbonToolBarStackPanel : StackPanel
    {
        /// <summary>
        ///   Initializes a new instance of the RibbonToolBarStackPanel class.
        /// </summary>
        public RibbonToolBarStackPanel()
        {
            Orientation = Orientation.Horizontal;
        }

        /// <summary>
        ///   Gets a collection containing the children of the QuickAcessToolbar's stackpanel.
        /// </summary>
        /// <param name="logicalParent">Logical parent of items of the returned collection.</param>
        /// <returns>Returns a collection containing the children of the QuickAcessToolbar's stackpanel.</returns>
        protected override UIElementCollection CreateUIElementCollection(FrameworkElement logicalParent)
        {
            return new UIElementCollection(this, TemplatedParent == null ? logicalParent : null);
        }
    }

    #endregion
}